package org.terramagnet.mvcframework;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource;
import org.springframework.context.NoSuchMessageException;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.util.WebUtils;

@ControllerAdvice
public final class GlobalExceptionHandler {

    private final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    private final Pattern IE_AGENT = Pattern.compile("msie ([\\d.]+)", Pattern.CASE_INSENSITIVE);
    private final ObjectMapper jsonUtil = new ObjectMapper();

    @Resource
    private MessageSource messageSource;
    @Value("#{config['mvc.error.view'] ?: null}")
    private String errorView;

    @ExceptionHandler(Exception.class)
    public Object defaultExceptionHandler(HttpServletRequest req, HttpServletResponse resp, Locale locale, Exception ex) throws Exception {
        PrimaryResponse response = new PrimaryResponse();
        ResponseStatus status;
        if (ex instanceof FriendlyException) {//应用自定义异常
            FriendlyException fe = (FriendlyException) ex;
            response.setCode(fe.getCode());
            response.setMessage(translateMessage(fe, locale));
        } else if ((status = AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class)) != null) {//对spring内置注解@ResponseStatus的支持
            response.setCode(status.value().value());
            response.setMessage(translateMessage(ex.getMessage(), locale));
        } else if (ex instanceof ServletRequestBindingException
            || (ex instanceof TypeMismatchException)
            || (ex instanceof HttpRequestMethodNotSupportedException)) {//spring mvc 基本入口错误
            throw ex;
        } else if (ex instanceof IllegalArgumentException) {
            response.setCode(400);
            response.setMessage(translateMessage("error.argument.illegal", locale));
        } else {
            response.setCode(500);
            response.setMessage(translateMessage(ex.getMessage(), locale));
        }
        logger.error(ex.getMessage(), ex);
        req.setAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE, response.getCode());
        req.setAttribute(WebUtils.ERROR_MESSAGE_ATTRIBUTE, response.getMessage());
        req.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex);

        if (response.getCode() >= 400) {
            resp.setStatus(response.getCode());
        } else {
            resp.setStatus(500);
        }

        if ("json".equals(errorView)
                || req.getHeader("Accept").contains("json")
                || (req.getHeader("X-Requested-With") != null && req.getHeader("X-Requested-With").contains("XMLHttpRequest"))) {
            String jsonContentType = jsonContentType(req.getHeader("User-Agent"));
            resp.setContentType(jsonContentType);
            jsonUtil.writeValue(resp.getWriter(), response);
            return null;
        } else if (StringUtils.isBlank(errorView)) {
            return new ResponseEntity(
                response.getMessage(),
                HttpStatus.valueOf(response.getCode())
            );
        }
        return errorView;
    }

    private String translateMessage(FriendlyException ex, Locale locale) {
        try {
            return ex.toFriendly(locale);
        } catch (NoSuchMessageException e) {
            return ex.getMessage();
        }
    }

    private String translateMessage(String message, Locale locale) {
        String[] texts = message.split("[|]");
        if (texts.length == 1) {
            try {
                return messageSource.getMessage(message, null, locale);
            } catch (NoSuchMessageException noSuchMessageException) {
                return message;
            }
        } else {
            StringBuilder builder = new StringBuilder();
            for (String text : texts) {
                try {
                    builder.append(messageSource.getMessage(text, null, locale)).append("<br />");
                } catch (NoSuchMessageException noSuchMessageException) {
                    builder.append(text).append("<br />");
                }
            }
            return builder.toString();
        }

    }

    private String jsonContentType(String userAgent) {
        Matcher matcher = IE_AGENT.matcher(userAgent);
        if (matcher.find()) {
            float version = NumberUtils.toFloat(matcher.group(1), 6.0f);
            if (version < 10) {
                return "text/json;charset=UTF-8";
            }
        }
        return "application/json;charset=UTF-8";
    }

    public void setErrorView(String errorView) {
        this.errorView = errorView;
    }
}
